﻿/************************************************************************************  
' Copyright (C) 2008 Anthony Bouch (http://www.58bits.com) under the terms of the
' Microsoft Public License (Ms-PL http://www.codeplex.com/precode/license)
'***********************************************************************************/
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Runtime.InteropServices;
using System.Diagnostics;
using ComponentFactory.Krypton.Toolkit;

namespace PreCodePlugin
{
    public partial class PreCodeWindow : KryptonForm
    {
        /// <summary>
        /// Constant for tab and indent settings
        /// </summary>
        private const int TAB_SIZE = 4;
        private const int OPTIONS_SPLITER_WIDTH = 187;
        private bool showOptions = true;
        private Mode applicationMode;

        public enum Mode
        {
            StandAlone,
            WLW
        } ;
        
        /// <summary>
        /// tabState holder for Tabstops enable/disable when entering/leaving the text box.
        /// </summary>
        readonly bool[] tabState = new bool[256];

        /// <summary>
        /// Colllection of syntaxhighlighter code/language names and alias attributes
        /// used as the data source for <see cref="cboSyntaxHighlighterClass"/> combobox.
        /// </summary>
        private SyntaxClassCollection codeClassItems;

        private string _content = string.Empty;
        /// <summary>
        /// Content property - uses to return the formatted string content to WLW via the <see cref="PreCodePlugin"/> 
        /// </summary>
        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }

        /// <summary>
        /// Our strategy interface for user settings, Windows Live Writer, or WinForms
        /// </summary>
        private readonly IPreCodeSettings userSettings;
        
        public PreCodeWindow()
        {
            InitializeComponent();
            headerGroupRight.ButtonSpecs[0].Click += headerGroupRight_Click;
            kryptonSplitHorizontal.SplitterDistance = 546;
            KeyPreview = true;
        }

       

        /// <summary>
        /// Constructor that takes PreCodeSettings - then calls the default constructor
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="mode"></param>
        public PreCodeWindow(IPreCodeSettings settings, Mode mode)
            : this()
        {
            userSettings = settings;
            applicationMode = mode;
        }

        /// <summary>
        /// Required because a multi-line textbox will not support CTRL-A text selection by default, and
        /// if a standard keydown handler is used, the system bell, or 'ding' sound will be
        /// still be heard.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == (Keys.A | Keys.Control))
            {
                txtContent.SelectionStart = 0;
                txtContent.SelectionLength = txtContent.Text.Length;
                txtContent.Focus();
                return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

       
        /// <summary>
        /// OnLoad
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            // define value of the Tab indent 
            int[] stops = { TAB_SIZE * 4 };
            // change the indent 
            SendMessage(txtContent.Handle, EM_SETTABSTOPS, 1, stops);

            //Set mode
            if (applicationMode == Mode.StandAlone)
            {
                kryptonButton5.Width = 200;
                kryptonButton5.Left = 500;
                kryptonButton5.Text = "C&opy to Clipboard and Close";
            }

            //Load user settings
            LoadSettings();  
        }

        /// <summary>
        /// OnClosing
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            
            //Save user settings
            SaveSettings();
        }

        #region Window Control Event Handlers

        /// <summary>
        /// Format the content and return DialogResult.OK
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOk_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(txtContent.Text))
            {
                //Clean tabs out
                SwapTabsForSpaces();

                Content = txtContent.Text;

                //Encode
                if (chkHtmlEncode.Checked)
                    Content = HttpUtility.HtmlEncode(Content);

                //Swap line endings for </br>
                if (chkUseBrs.Checked)
                    Content = Content.Replace(Environment.NewLine, "<br />");

                //Do the rest...
                if (cboSuroundWith.SelectedIndex != 0)
                {
                    switch (cboSuroundWith.SelectedIndex)
                    {
                        case 1:
                            if (cboSyntaxHighlighterClass.SelectedIndex == 0)
                            {
                                Content = String.Format("<pre>\n\r{0}\n\r</pre>", Content);
                            }
                            else
                            {
                                Content = String.Format("<pre class=\"{0}\">\n{1}\n</pre>", GetClassOptions(), Content);
                            }
                            break;
                        case 2:
                            if (cboSyntaxHighlighterClass.SelectedIndex == 0)
                            {
                                Content = String.Format("<textarea rows=\"{0}\">\n{1}\n</textarea>", txtContent.Lines.Length, Content);
                            }
                            else
                            {
                                Content = String.Format("<textarea class=\"{0}\" rows=\"{1}\">\n{2}\n</textarea>", GetClassOptions(), txtContent.Lines.Length, Content);
                                
                            }
                            break;
                        case 3:
                            Content = String.Format("<blockquote>\n{0}\n</blockquote>", Content);
                            break;
                    }
                }

                DialogResult = DialogResult.OK;
            }
            else
            {
                DialogResult = DialogResult.Cancel;
            }
            
            Close();
        }

        private string GetClassOptions()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("brush: ");
            sb.Append(((SyntaxClass)cboSyntaxHighlighterClass.SelectedItem).ClassAttribute);
            sb.Append(";");
            if (chkShowRuler.Checked)
                sb.Append(" ruler: true;");
            if(chkNotDisplayGutter.Checked)
                sb.Append(" gutter: false;");
            if(chkNotShowToolBar.Checked)
                sb.Append(" toolbar: false;");
            if(chkCollapseBlock.Checked)
                sb.Append(" collapse: true;");
            if (chkDoNotAutoLink.Checked)
                sb.Append(" auto-links: false;");
            if (chkTurnOffSmartTabs.Checked)
                sb.Append(" smart-tabs: false;");
            if (numStartLine.Value > 1)
                sb.AppendFormat(" first-line: {0};", (int)numStartLine.Value);
            if(!String.IsNullOrEmpty(txtHighlightLines.Text.Trim()))
                sb.AppendFormat(" highlight: [{0}];", txtHighlightLines.Text.Trim());

            return sb.ToString();
        }


        /// <summary>
        /// Return DialogResult.Cancel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            Close();
        }        
        
        /// <summary>
        /// Increase Indent for line or selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnIncreaseIndent_Click(object sender, EventArgs e)
        {
            bool isFirstLine = true;
            int firstLine = 0, lastLine = 0;
            int savedSelectionLength = txtContent.SelectionLength;
            int newSelectionStart = txtContent.SelectionStart;
            int charCount = 0;

            GetLineRange(ref firstLine, ref lastLine);

            SwapTabsForSpaces();

            string[] lines = new string[txtContent.Lines.Length];

            for (int i = 0; i < txtContent.Lines.Length; i++)
            {
                if (i >= firstLine && i <= lastLine)
                {
                    lines[i] = " ".PadLeft(TAB_SIZE) + txtContent.Lines[i];
                    charCount += lines[i].Length + Environment.NewLine.Length;
                    if (isFirstLine)
                    {
                        newSelectionStart = (charCount - (lines[i].Length + Environment.NewLine.Length)) + GetLeadingWhiteSpaceCount(lines[i]);
                        isFirstLine = false;
                    }
                    else
                    {
                        savedSelectionLength += TAB_SIZE;
                    }
                }
                else
                {
                    lines[i] = txtContent.Lines[i];
                    charCount += lines[i].Length + Environment.NewLine.Length;
                }
            }

            txtContent.Lines = lines;
            txtContent.SelectionStart = newSelectionStart;
            txtContent.SelectionLength = savedSelectionLength;
            txtContent.Focus();
        }

        /// <summary>
        /// Decrease indent for line or selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDecreaseIndent_Click(object sender, EventArgs e)
        {
            bool isFirstLine = true;
            int dedentedAmount = 0;
            int firstLine = 0, lastLine = 0;
            int savedSelectionLength = txtContent.SelectionLength;
            int newSelectionStart = txtContent.SelectionStart;            
            int charCount = 0;

            GetLineRange(ref firstLine, ref lastLine);

            SwapTabsForSpaces();

            string[] lines = new string[txtContent.Lines.Length];

            for (int i = 0; i < txtContent.Lines.Length; i++)
            {
                if (i >= firstLine && i <= lastLine)
                {
                    int leadingCount = GetLeadingWhiteSpaceCount(txtContent.Lines[i]);
                    if (leadingCount >= TAB_SIZE)
                    {
                        lines[i] = txtContent.Lines[i].Substring(TAB_SIZE);
                        dedentedAmount = TAB_SIZE;
                    }
                    else if (leadingCount > 0)
                    {
                        lines[i] = txtContent.Lines[i].Substring(leadingCount);
                        dedentedAmount = leadingCount;
                    }
                    else
                    {
                        lines[i] = txtContent.Lines[i];
                    }
                    
                    charCount += lines[i].Length + Environment.NewLine.Length;
                    
                    if (isFirstLine)
                    {
                        newSelectionStart = (charCount - (lines[i].Length + Environment.NewLine.Length)) + GetLeadingWhiteSpaceCount(lines[i]);
                        isFirstLine = false;
                    }
                    else
                    {
                        if (savedSelectionLength > dedentedAmount)
                        {
                            savedSelectionLength -= dedentedAmount;                            
                        }

                    }

                    dedentedAmount = 0;
                }
                else
                {
                    lines[i] = txtContent.Lines[i];
                    charCount += lines[i].Length + Environment.NewLine.Length;
                }
                
            }

            txtContent.Lines = lines;
            txtContent.SelectionStart = newSelectionStart;
            txtContent.SelectionLength = savedSelectionLength;
            txtContent.Focus();
        }


        /// <summary>
        /// Try to fix any initial indentation mis-alignment
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnFixIndentation_Click(object sender, EventArgs e)
        {
            if (txtContent.Lines.Length > 1)
            {
                int firsLineOffset = GetLeadingWhiteSpaceCount(txtContent.Lines[0]);
                DeDent(firsLineOffset);
                int lastLineOffset = GetLeadingWhiteSpaceCount(txtContent.Lines[txtContent.Lines.Length - 1]);
                DeDent(lastLineOffset);
            }
        }

        void headerGroupRight_Click(object sender, EventArgs e)
        {
            ShowHideOptions();
        }

        /// <summary>
        /// If we're not surounding with any element, or using a blockquote - then disable cboSyntaxHighlighterClass 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cboSuroundWith_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cboSuroundWith.SelectedIndex == 0 || cboSuroundWith.SelectedIndex == 3)
            {
                grpSyntaxOptions.Enabled = false;               
            }
            else
            {
                grpSyntaxOptions.Enabled = true;                
            }
        }
        
        /// <summary>
        /// Enter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtContent_Enter(object sender, EventArgs e)
        {
            DisableTabStops(this, 0);
        }      

        /// <summary>
        /// Validating
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtContent_Validating(object sender, CancelEventArgs e)
        {
            EnableTabStops(this, 0);
        }

        
        private void lnkSyntaxHighlighter_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            try
            {
                Process.Start("http://alexgorbatchev.com/wiki/SyntaxHighlighter");
            }
            catch (Exception ex)
            {
                HandleException("Open SyntaxHighlighter Line()", ex);
            }
        }

        private void btnCheckForUpdate_Click(object sender, EventArgs e)
        {
            UpdateWindow updater = new UpdateWindow();
            updater.ShowDialog(this);
        }          

        #endregion             

        #region Private Helper Methods
        
        //Helper method to change the default tab spacing of a textbox to TAB_SIZE characters as opposed to the default 8
        //http://www.shahine.com/omar/ChangingTheTabStopInATextbox.aspx 
        private const int EM_SETTABSTOPS = 0x00CB;
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr h, int msg, int wParam, int[] lParam);

        private void DisableTabStops(Control control, int stateCounter)
        {
            //Disable TabStops for all other controls to enale tabbing inside the Textbox
            for (int i = 0; i < control.Controls.Count; i++)
            {
                tabState[stateCounter] = control.Controls[i].TabStop;
                control.Controls[i].TabStop = false;
                stateCounter++;
                if (control.Controls[i].Controls.Count > 0)
                {
                    DisableTabStops(control.Controls[i], stateCounter);
                }
            }
        }

        private void EnableTabStops(Control control, int stateCounter)
        {
            //Disable TabStops for all other controls to enale tabbing inside the Textbox
            for (int i = 0; i < control.Controls.Count; i++)
            {
                control.Controls[i].TabStop = tabState[stateCounter];
                stateCounter++;
                if (control.Controls[i].Controls.Count > 0)
                {
                    EnableTabStops(control.Controls[i], stateCounter);
                }
            }
        }

        /// <summary>
        /// Helper method to get current line, or line range of current selection
        /// </summary>
        /// <param name="first"></param>
        /// <param name="last"></param>
        private void GetLineRange(ref int first, ref int last)
        {
            try
            {
                bool foundFirst = false;
                bool foundLast = false;
                int startPosition = txtContent.SelectionStart;
                int stopPosition = startPosition + txtContent.SelectionLength;
                int charCount = 0;
                for (int i = 0; i < txtContent.Lines.Length; i++)
                {
                    charCount += txtContent.Lines[i].Length + Environment.NewLine.Length; //Add two bytes for line endings
                    if (startPosition < charCount && !foundFirst)
                    {
                        first = i;
                        foundFirst = true;
                    }

                    if (stopPosition < charCount && !foundLast)
                    {
                        last = i;
                        foundLast = true;
                    }

                    if (foundFirst && foundLast)
                        break;
                }
            }
            catch (Exception ex)
            {
                HandleException("GetLineRange()", ex);
            }
        }

        /// <summary>
        /// Determine the amount of whitespace at the begining of a line/string
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private int GetLeadingWhiteSpaceCount(string input)
        {
            try
            {
                if (input == null)
                    throw new ArgumentNullException("input");

                char[] line = input.ToCharArray();
                int count = 0;
                foreach (char c in line)
                {
                    if (Char.IsWhiteSpace(c))
                        count++;
                    else
                        break;
                }

                return count;
            }
            catch (Exception ex)
            {
                HandleException("GetLeadingWhiteSpaceCount()", ex);
                return 0;
            }
        }

        /// <summary>
        /// Reduce the indent of all lines in <see cref="txtContent"/>.
        /// </summary>
        /// <param name="amount"></param>
        private void DeDent(int amount)
        {
            try
            {
                SwapTabsForSpaces();

                string[] lines = new string[txtContent.Lines.Length];
                int currentOffset;
                for (int i = 0; i < txtContent.Lines.Length; i++)
                {
                    currentOffset = GetLeadingWhiteSpaceCount(txtContent.Lines[i]);
                    if (currentOffset >= amount)
                        lines[i] = txtContent.Lines[i].Substring(amount);
                    else
                        lines[i] = txtContent.Lines[i];
                }

                txtContent.Lines = lines;
            }
            catch (Exception ex)
            {
                HandleException("DeDent()", ex);
            }
        }

        /// <summary>
        /// Show or hide the Options panel
        /// </summary>
        private void ShowHideOptions()
        {
            // Suspend layout changes until all splitter properties have been updated
            kryptonSplitHorizontal.SuspendLayout();
            
            if (showOptions)
            {
                if (kryptonSplitHorizontal.Panel2.Width < OPTIONS_SPLITER_WIDTH)
                {
                    // Make the header group fixed just as the new height
                    kryptonSplitHorizontal.Panel1MinSize -= OPTIONS_SPLITER_WIDTH;
                    kryptonSplitHorizontal.SplitterDistance -= OPTIONS_SPLITER_WIDTH;

                    // Change header to be horizontal and button to far edge
                    headerGroupRight.HeaderPositionPrimary = VisualOrientation.Top;
                    headerGroupRight.ButtonSpecs[0].Edge = PaletteRelativeEdgeAlign.Far;
                    headerGroupRight.Collapsed = false;
                }

                showOptions = false;
            }
            else 
            {
                if (kryptonSplitHorizontal.Panel2.Width > OPTIONS_SPLITER_WIDTH)
                {

                    kryptonSplitHorizontal.Panel1MinSize += OPTIONS_SPLITER_WIDTH;
                    kryptonSplitHorizontal.SplitterDistance += OPTIONS_SPLITER_WIDTH;

                    // Change header to be vertical and button to near edge
                    headerGroupRight.HeaderPositionPrimary = VisualOrientation.Right;
                    headerGroupRight.ButtonSpecs[0].Edge = PaletteRelativeEdgeAlign.Near;
                    headerGroupRight.Collapsed = true;
                }

                showOptions = true;
            }

            kryptonSplitHorizontal.ResumeLayout();
        }

        /// <summary>
        /// Load user settings
        /// </summary>
        private void LoadSettings()
        {
            try
            {
                codeClassItems = GetCodeClassList();
                cboSyntaxHighlighterClass.DataSource = codeClassItems;
                cboSyntaxHighlighterClass.SelectedIndex = cboSyntaxHighlighterClass.Items.IndexOf(codeClassItems[userSettings.SyntaxClassAttribute]);
                
                showOptions = userSettings.ShowOptions;
                ShowHideOptions();
                cboSuroundWith.SelectedIndex = 0;
                
                cboSuroundWith.SelectedIndex = userSettings.SuroundWith;
                chkHtmlEncode.Checked = userSettings.HtmlEncode;
                chkUseBrs.Checked = userSettings.UseBrs;

                if (cboSuroundWith.SelectedIndex == 0 || cboSuroundWith.SelectedIndex == 3)
                    grpSyntaxOptions.Enabled = false;
                else
                    grpSyntaxOptions.Enabled = true;

                
                chkNotDisplayGutter.Checked = userSettings.DoNotDisplayGutter;
                chkNotShowToolBar.Checked = userSettings.DoNotShowToolBar;
                chkCollapseBlock.Checked = userSettings.CollapseBlock;
                chkShowRuler.Checked = userSettings.ShowRuler;
                chkDoNotAutoLink.Checked = userSettings.DoNotAutoLink;
                chkTurnOffSmartTabs.Checked = userSettings.DoNotUseSmartTabs;
                numStartLine.Value = userSettings.LineCountStart;
                
            }
            catch (Exception ex)
            {
                HandleException("LoadSettings()", ex);
            }
        }

        /// <summary>
        /// Save user settings
        /// </summary>
        private void SaveSettings()
        {
            try
            {
                userSettings.ShowOptions = !showOptions;
                userSettings.SuroundWith = cboSuroundWith.SelectedIndex;
                userSettings.HtmlEncode = chkHtmlEncode.Checked;
                userSettings.UseBrs = chkUseBrs.Checked;

                SyntaxClass selected = cboSyntaxHighlighterClass.SelectedItem as SyntaxClass;
                if (selected != null)
                    userSettings.SyntaxClassAttribute = selected.ClassAttribute;
                userSettings.DoNotDisplayGutter = chkNotDisplayGutter.Checked;
                userSettings.DoNotShowToolBar = chkNotShowToolBar.Checked;
                userSettings.CollapseBlock = chkCollapseBlock.Checked;
                userSettings.ShowRuler = chkShowRuler.Checked;
                userSettings.DoNotAutoLink = chkDoNotAutoLink.Checked;
                userSettings.DoNotUseSmartTabs = chkTurnOffSmartTabs.Checked;
                userSettings.LineCountStart = (int)numStartLine.Value;
                
            }
            catch(Exception ex)
            {
                HandleException("SaveSettings()", ex);
            }
        }

        /// <summary>
        /// Helper method to create a collection of syntaxhighlighter code/language names and alias attributes
        /// </summary>
        /// <returns></returns>
        private SyntaxClassCollection GetCodeClassList()
        {
            SyntaxClassCollection items = new SyntaxClassCollection();
            items.Add(new SyntaxClass("None", "none"));
            items.Add(new SyntaxClass("Bash", "bash"));
            items.Add(new SyntaxClass("C++", "cpp"));
            items.Add(new SyntaxClass("C#", "csharp"));
            items.Add(new SyntaxClass("CSS", "css"));
            items.Add(new SyntaxClass("Delphi", "delphi"));
            items.Add(new SyntaxClass("Diff", "diff"));
            items.Add(new SyntaxClass("Groovy", "groovy"));
            items.Add(new SyntaxClass("Java", "java"));
            items.Add(new SyntaxClass("JavaScript", "js"));
            items.Add(new SyntaxClass("Perl", "perl"));
            items.Add(new SyntaxClass("PHP", "php"));
            items.Add(new SyntaxClass("Plain Text", "plain"));
            items.Add(new SyntaxClass("Python", "py"));
            items.Add(new SyntaxClass("Ruby", "ruby"));
            items.Add(new SyntaxClass("Scala", "scala"));
            items.Add(new SyntaxClass("SQL", "sql"));
            items.Add(new SyntaxClass("Visual Basic", "vb"));
            items.Add(new SyntaxClass("XML/HTML", "xml"));

            return items;
        }

        private void SwapTabsForSpaces()
        {
            // swap any tab whitespaces for spaces since amount will always be in space.
            txtContent.Text = txtContent.Text.Replace("\t", " ".PadLeft(TAB_SIZE));
        }

        private static void HandleException(string member, Exception ex)
        {
            MessageBox.Show(String.Format("We're sorry but an error occurred in the PreCode plugin at {0}. Error message: {1}", member, ex.Message));
        }

        #endregion               

        private void kryptonButton7_Click(object sender, EventArgs e)
        {
            AboutWindow about = new AboutWindow();
            about.ShowDialog(this);
        }
    }
}
